package neo.aoi;

import java.util.ArrayList;
import java.util.List;

import artofillusion.math.BoundingBox;
import artofillusion.math.Vec3;

/**
 * This class represents a voxel. A voxel is a horizontally and vertically
 * straight cube, around a specified center, and with edges of the specified
 * size. For example, if the voxel center is located al the origin (0,0,0) and
 * its edges are of size 1 (resolution = 1), the vertices of the voxel will be
 * located at the following locations:
 * 
 * <ul>
 * <li>(-0.5,-0.5,-0.5)</li>
 * <li>(-0.5,-0.5, 0.5)</li>
 * <li>(-0.5, 0.5,-0.5)</li>
 * <li>(-0.5, 0.5, 0.5)</li>
 * <li>( 0.5,-0.5,-0.5)</li>
 * <li>( 0.5,-0.5, 0.5)</li>
 * <li>( 0.5, 0.5,-0.5)</li>
 * <li>( 0.5, 0.5, 0.5)</li>
 * </ul>
 * 
 * @author Carlos Martinez
 */
public class Voxel {
	private Vec3 ver_0;

	private Vec3 ver_1;

	private Vec3 ver_2;

	private Vec3 ver_3;

	private Vec3 ver_4;

	private Vec3 ver_5;

	private Vec3 ver_6;

	private Vec3 ver_7;

	private double r; // half of the edge size

	private BoundingBox _bounds;

	/**
	 * Creates an instance of a Voxel around the given center and resolution
	 * 
	 * @param center
	 *            The voxel center
	 * @param res
	 *            The voxel resolution
	 */
	public Voxel(Vec3 center, double res) {
		r = res / 2;

		ver_0 = new Vec3(center.x - r, center.y - r, center.z - r);
		ver_1 = new Vec3(center.x - r, center.y - r, center.z + r);
		ver_2 = new Vec3(center.x - r, center.y + r, center.z - r);
		ver_3 = new Vec3(center.x - r, center.y + r, center.z + r);
		ver_4 = new Vec3(center.x + r, center.y - r, center.z - r);
		ver_5 = new Vec3(center.x + r, center.y - r, center.z + r);
		ver_6 = new Vec3(center.x + r, center.y + r, center.z - r);
		ver_7 = new Vec3(center.x + r, center.y + r, center.z + r);
	}

	/**
	 * Returns a list of the triangle which form the faces of this voxel
	 * 
	 * @return A List of Triangle objects which represent the faces of the
	 *         voxel.
	 */
	public List<Triangle> getFacetList() {
		List<Triangle> facets = new ArrayList<Triangle>();
		facets.add(new Triangle(ver_0, ver_1, ver_2));
		facets.add(new Triangle(ver_1, ver_2, ver_3));

		facets.add(new Triangle(ver_0, ver_1, ver_4));
		facets.add(new Triangle(ver_1, ver_4, ver_5));

		facets.add(new Triangle(ver_0, ver_2, ver_6));
		facets.add(new Triangle(ver_0, ver_4, ver_6));

		facets.add(new Triangle(ver_1, ver_3, ver_7));
		facets.add(new Triangle(ver_1, ver_5, ver_7));

		facets.add(new Triangle(ver_2, ver_3, ver_7));
		facets.add(new Triangle(ver_2, ver_6, ver_7));

		facets.add(new Triangle(ver_4, ver_5, ver_6));
		facets.add(new Triangle(ver_5, ver_6, ver_7));

		return facets;
	}

	/**
	 * Returns the BoundingBox equivalent to this voxel
	 * 
	 * @return A BoundingBox with the same vertices as the Voxel
	 */
	public BoundingBox getBoundingBox() {
		if (_bounds == null)
			_bounds = new BoundingBox(ver_0, ver_7);
		return _bounds;
	}

	/**
	 * Checks if this voxel intersects with a given Triangle.
	 * 
	 * @param target
	 *            The Triangle to check for intersection
	 * @return True if the given Triangle and this voxel intersect, false
	 *         otherwise
	 */
	public boolean intersects1(Triangle target) {
		/* First compare all three vertexes with all six face-planes */
		/* If any vertex is inside the cube, return immediately! */
		BoundingBox bounds = getBoundingBox();
		if (bounds.contains(target.getVertex(0)))
			return true;
		if (bounds.contains(target.getVertex(1)))
			return true;
		if (bounds.contains(target.getVertex(2)))
			return true;

		List<Triangle> facets = getFacetList();
		for (Triangle t : facets) {
			if (target.intersects(t))
				return true;
		}
		return false;
	}

	private static final int BOTTOM = 0;

	private static final int TOP = 1;

	private static final int LEFT = 2;

	private static final int RIGHT = 3;

	private static final int FRONT = 4;

	private static final int BACK = 5;

	private static final int T_BOTTOM_0 = 0;

	private static final int T_BOTTOM_1 = 1;

	private static final int T_TOP_0 = 2;

	private static final int T_TOP_1 = 3;

	private static final int T_LEFT_0 = 4;

	private static final int T_LEFT_1 = 5;

	private static final int T_RIGHT_0 = 6;

	private static final int T_RIGHT_1 = 7;

	private static final int T_FRONT_0 = 8;

	private static final int T_FRONT_1 = 9;

	private static final int T_BACK_0 = 10;

	private static final int T_BACK_1 = 11;

	/**
	 * Checks whether a given triangle intersects with this Voxel. Returns true
	 * if the target Triangle and this Voxel intersect, false otherwise.
	 * 
	 * @param target
	 *            The triangle to be checked for intersection
	 * @return true if the target Triangle and this Voxel intersect, false
	 *         otherwise.
	 */
	public boolean intersects(Triangle target) {
		/* First compare all three vertexes with all six face-planes */
		/* If any vertex is inside the cube, return immediately! */
		BoundingBox bounds = getBoundingBox();
		Vec3 a = target.getVertex(0);
		if (bounds.contains(a))
			return true;
		Vec3 b = target.getVertex(1);
		if (bounds.contains(b))
			return true;
		Vec3 c = target.getVertex(2);
		if (bounds.contains(c))
			return true;

		Plane[] plane = new Plane[6];
		plane[BOTTOM] = new Plane(ver_0, ver_1, ver_4);
		plane[TOP] = new Plane(ver_2, ver_3, ver_7);
		plane[LEFT] = new Plane(ver_0, ver_1, ver_2);
		plane[RIGHT] = new Plane(ver_4, ver_5, ver_6);
		plane[FRONT] = new Plane(ver_1, ver_3, ver_7);
		plane[BACK] = new Plane(ver_0, ver_2, ver_6);

		Triangle[] t_faces = new Triangle[12];
		t_faces[T_BOTTOM_0] = new Triangle(ver_0, ver_1, ver_4);
		t_faces[T_BOTTOM_1] = new Triangle(ver_1, ver_4, ver_5);

		t_faces[T_TOP_0] = new Triangle(ver_2, ver_3, ver_7);
		t_faces[T_TOP_1] = new Triangle(ver_2, ver_6, ver_7);

		t_faces[T_LEFT_0] = new Triangle(ver_0, ver_1, ver_2);
		t_faces[T_LEFT_1] = new Triangle(ver_1, ver_2, ver_3);

		t_faces[T_RIGHT_0] = new Triangle(ver_4, ver_5, ver_6);
		t_faces[T_RIGHT_1] = new Triangle(ver_5, ver_6, ver_7);

		t_faces[T_FRONT_0] = new Triangle(ver_1, ver_3, ver_7);
		t_faces[T_FRONT_1] = new Triangle(ver_1, ver_5, ver_7);

		t_faces[T_BACK_0] = new Triangle(ver_0, ver_2, ver_6);
		t_faces[T_BACK_1] = new Triangle(ver_0, ver_4, ver_6);

		for (int face = 0; face < 6; face++) {
			// Find at least 1 point on other side of plane
			boolean sign_a = plane[face].distance(a) > 0;
			boolean sign_b = plane[face].distance(b) > 0;
			boolean sign_c = plane[face].distance(c) > 0;

			if (sign_a == sign_b && sign_a == sign_c) {
				// they are all equal,
				// Triangle does not cross this face-plane
				// continue...
				continue;
			} else {
				int idx_0, idx_1;
				if (face == BOTTOM) {
					idx_0 = T_BOTTOM_0;
					idx_1 = T_BOTTOM_1;
				} else if (face == TOP) {
					idx_0 = T_TOP_0;
					idx_1 = T_TOP_1;
				} else if (face == FRONT) {
					idx_0 = T_FRONT_0;
					idx_1 = T_FRONT_1;
				} else if (face == BACK) {
					idx_0 = T_BACK_0;
					idx_1 = T_BACK_1;
				} else if (face == RIGHT) {
					idx_0 = T_RIGHT_0;
					idx_1 = T_RIGHT_1;
				} else if (face == LEFT) {
					idx_0 = T_LEFT_0;
					idx_1 = T_LEFT_1;
				} else {
					System.out.println("IDX_NOT SET!!!");
					System.exit(0);
					return false;
				}

				boolean inter_0 = target.intersects(t_faces[idx_0]);
				boolean inter_1 = target.intersects(t_faces[idx_1]);
				if (inter_0 || inter_1)
					return true;
			}
		}
		return false;
	}
}
